package org.finesys.common.core.config;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HeaderElement;
import org.apache.http.HeaderElementIterator;
import org.apache.http.HttpHost;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.config.Registry;
import org.apache.http.config.RegistryBuilder;
import org.apache.http.conn.ConnectionKeepAliveStrategy;
import org.apache.http.conn.socket.ConnectionSocketFactory;
import org.apache.http.conn.socket.PlainConnectionSocketFactory;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.impl.client.DefaultHttpRequestRetryHandler;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.message.BasicHeaderElementIterator;
import org.apache.http.protocol.HTTP;
import org.apache.http.protocol.HttpContext;
import org.apache.http.ssl.SSLContexts;
import org.finesys.common.core.handler.RestErrorHandler;
import org.finesys.common.core.interceptor.RestLoggingInterceptor;
import org.finesys.common.core.properties.RestClientProperties;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.http.client.BufferingClientHttpRequestFactory;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.StringHttpMessageConverter;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import javax.net.ssl.SSLContext;
import java.nio.charset.StandardCharsets;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * RestTemplate配置类
 */
@Slf4j
@AutoConfiguration
@RequiredArgsConstructor
@EnableConfigurationProperties(RestClientProperties.class)
@ConditionalOnProperty(prefix = "spring.restTemplate", name = "enabled", havingValue = "true")
public class RestTemplateConfiguration {

    private final RestClientProperties restClientProperties;

    /**
     * 配置长连接策略
     */
    @Bean
    public ConnectionKeepAliveStrategy connectionKeepAliveStrategy() {
        return new ConnectionKeepAliveStrategy() {
            @Override
            public long getKeepAliveDuration(HttpResponse response, HttpContext context) {
                HeaderElementIterator headerElementIterator = new BasicHeaderElementIterator(response.headerIterator(HTTP.CONN_KEEP_ALIVE));
                while (headerElementIterator.hasNext()) {
                    HeaderElement headerElement = headerElementIterator.nextElement();
                    String param = headerElement.getName();
                    String value = headerElement.getValue();
                    if (StringUtils.hasText(value) && "timeout".equalsIgnoreCase(param)) {
                        try {
                            //1.服务器有时候会告诉客户端长连接的超时时间，如果有则设置为服务器的返回值
                            return Long.parseLong(value) * 1000;
                        } catch (NumberFormatException e) {
                            log.error("解析长连接过期时间异常", e);
                        }
                    }

                }
                //2.如果服务器没有返回超时时间则采用配置的时间
                //a.如果请求目标地址,单独配置了长连接保持时间,使用该配置 b.否则使用配置的的默认长连接保持时间keepAliveTime
                HttpHost httpHost = (HttpHost) context.getAttribute(HttpClientContext.HTTP_TARGET_HOST);
                Optional<Map.Entry<String, Long>> any = Optional.ofNullable(restClientProperties.getHttpClient().getKeepAliveTargetHosts()).orElseGet(HashMap::new).entrySet().stream().filter(e -> e.getKey().equalsIgnoreCase(httpHost.getHostName())).findAny();
                return any.map(Map.Entry::getValue).orElse(restClientProperties.getHttpClient().getKeepAliveTime());
            }
        };
    }

    /**
     * 配置httpClient
     */
    @Bean
    public HttpClient httpClient(ConnectionKeepAliveStrategy connectionKeepAliveStrategy) {
        HttpClientBuilder httpClientBuilder = HttpClientBuilder.create();
        SSLContext sslContext;
        try {
            sslContext = SSLContexts.custom().loadTrustMaterial(null, (X509Certificate[] var1, String var2) -> true).build();
        } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException e) {
            throw new RuntimeException(e);
        }
        SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext, NoopHostnameVerifier.INSTANCE);
        Registry<ConnectionSocketFactory> socketFactoryRegistry = RegistryBuilder.<ConnectionSocketFactory>create()
                .register("http", PlainConnectionSocketFactory.getSocketFactory())
                .register("https", sslConnectionSocketFactory).build();
        //使用Httpclient连接池的方式配置(推荐)，同时支持netty，okHttp以及其他http框架
        PoolingHttpClientConnectionManager poolingHttpClientConnectionManager = new PoolingHttpClientConnectionManager(socketFactoryRegistry);
        //最大tcp连接数
        poolingHttpClientConnectionManager.setMaxTotal(restClientProperties.getHttpClient().getMaxTotal());
        //同路由最大tcp连接数
        poolingHttpClientConnectionManager.setDefaultMaxPerRoute(restClientProperties.getHttpClient().getDefaultMaxPerRoute());
        //配置连接池
        httpClientBuilder.setConnectionManager(poolingHttpClientConnectionManager);
        //是否允许重试和重试次数
        httpClientBuilder.setRetryHandler(new DefaultHttpRequestRetryHandler(restClientProperties.getHttpClient().getRetryCount(), restClientProperties.getHttpClient().isRequestSentRetryEnabled()));
        //设置长连接保持策略
        httpClientBuilder.setKeepAliveStrategy(connectionKeepAliveStrategy);
        return httpClientBuilder.build();
    }

    /**
     * 配置httpclient工厂
     */
    @Bean
    public ClientHttpRequestFactory clientHttpRequestFactory(HttpClient httpClient) {
        HttpComponentsClientHttpRequestFactory httpRequestFactory = new HttpComponentsClientHttpRequestFactory();
        httpRequestFactory.setHttpClient(httpClient);
        //客户端和服务端建立连接的超时时间,这里最大只能是20s,因为linux操作系统的tcp进行三次握手的默认超时时间是20s,,即便设置100s也是在20s后报错
        httpRequestFactory.setConnectTimeout(restClientProperties.getConnectTimeout());
        //即socketTime,数据响应读取超时时间，指的是两个相邻的数据包的间隔超时时间
        httpRequestFactory.setReadTimeout(restClientProperties.getReadTimeOut());
        //从连接池获取连接的等待时间
        httpRequestFactory.setConnectionRequestTimeout(restClientProperties.getConnectionRequestTimeout());
        //必须使用BufferingClientHttpRequestFactory这个包装类，否则默认实现只允许读取一次响应流，日志输出那里读取之后，请求调用处再读取时会报错
        return new BufferingClientHttpRequestFactory(httpRequestFactory);
    }

    /**
     * 配置RestTemplate
     */
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory clientHttpRequestFactory) {
        RestTemplate restTemplate = new RestTemplate(clientHttpRequestFactory);
        //改为UTF-8格式
        List<HttpMessageConverter<?>> converterList = restTemplate.getMessageConverters();
        if (!converterList.isEmpty()) {
            converterList.forEach(converter -> {
                if (converter instanceof StringHttpMessageConverter) {
                    ((StringHttpMessageConverter) converter).setDefaultCharset(StandardCharsets.UTF_8);
                }
            });
        }
        //设置自定义异常
        restTemplate.setErrorHandler(new RestErrorHandler());
        //设置日志信息
        restTemplate.getInterceptors().add(new RestLoggingInterceptor());
        return restTemplate;
    }
}
